// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/exchange/base/order/rate_tracker.h"
#include "coin2/exchange/order/connection.pb.h"

constexpr auto NS = 1'000'000'000LL;
constexpr auto EXAMPLE_FUTURES_BITMEX = R"(
"connection_config": {
  "key_filepath": "/home/ec2-user/config/trade/xunke06/bitmex/trade_key.json",
  "rate_limit_info": {
    "futures_bitmex": {
      "req_all": {
        "secs": 60,
        "reqs": 60
      },
      "ord_sec": {
        "secs": 1,
        "reqs": 10
      }
    }
  }
})";

class BitmexRateTracker : public coin2::exchange::base::order::IRateTracker {
  using ActionType = coin2::exchange::base::order::IRateTracker::ActionType;
  using NativeClientConfig = coin2::exchange::order::OrderConnectionConfig;
  using RateLimitReportWriter = coin2::exchange::base::order::RateLimitReportWriter;
  using OrderType = coin::proto::OrderType;

  struct RatioValveConfig {
    RatioValveConfig() = default;
    double ratio_valve_query_order = 1.0;
    double ratio_valve_maker_order = 1.0;
    double ratio_valve_taker_order = 1.0;
    double ratio_valve_amend_order = 1.0;
    double ratio_valve_cancel_order = 1.0;
  };

  class BitmexRateTrackerImpl : public coin2::exchange::base::order::RateTracker {
   public:
    explicit BitmexRateTrackerImpl(
        int64_t window_size,
        double query_order_weight,
        double maker_order_weight,
        double taker_order_weight,
        double amend_order_weight,
        double cancel_order_weight,
        const std::string& name)
        : coin2::exchange::base::order::RateTracker(window_size),
          query_order_weight_{query_order_weight},
          maker_order_weight_{maker_order_weight},
          taker_order_weight_{taker_order_weight},
          amend_order_weight_{amend_order_weight},
          cancel_order_weight_{cancel_order_weight},
          name_{name} {
      DLOG(INFO) << "\nname: " << name_ << "\nquery_order_ratio_valve=" << query_order_weight_
                 << "\nmaker_order_ratio_valve=" << maker_order_weight_
                 << "\ntaker_order_ratio_valve=" << taker_order_weight_
                 << "\namend_order_ratio_valve=" << amend_order_weight_
                 << "\ncancel_order_ratio_valve=" << cancel_order_weight_;
    }

    bool HasEnoughRateLimitByType(int64_t timestamp, ActionType action_type, bool is_maker_order) {
      auto max = MaxReq();
      auto rem = GetRemaining(timestamp);
      auto sum = max - rem;
      auto weight = query_order_weight_;
      switch (action_type) {
        case ActionType::QUERY:
          weight = query_order_weight_;
          break;
        case ActionType::PLACE:
          weight = is_maker_order ? maker_order_weight_ : taker_order_weight_;
          break;
        case ActionType::AMEND:
          weight = amend_order_weight_;
          break;
        case ActionType::CANCEL:
          weight = cancel_order_weight_;
          break;
        default:
          // shouldn't reach here
          break;
      }
      double used_weight = static_cast<double>(sum) / max;

      ///////////
      // std::string action_type_str = "";
      // switch (action_type) {
      //   case ActionType::QUERY:
      //     action_type_str = "QUERY";
      //     break;
      //   case ActionType::PLACE:
      //     action_type_str = "PLACE";
      //     break;
      //   case ActionType::AMEND:
      //     action_type_str = "AMEND";
      //     break;
      //   case ActionType::CANCEL:
      //     action_type_str = "CANCEL";
      //     break;
      //   default:
      //     break;
      // }
      // std::string order_type_str = "";
      // if (is_maker_order) {
      //   order_type_str = "order_maker";
      // } else {
      //   order_type_str = "order_taker";
      // }
      // DLOG(INFO) << "name: " << name_ << ", info: num_rate_limit=" << max
      //            << ", num_remain=" << rem << ", num_used=" << sum
      //            << ", used_ratio=" << used_weight << ", limit_ratio=" << weight
      //            << ", action_type=" << action_type_str << ", order_type=" << order_type_str;
      ///////////

      return used_weight < weight;
    }

   private:
    const double query_order_weight_;
    const double maker_order_weight_;
    const double taker_order_weight_;
    const double amend_order_weight_;
    const double cancel_order_weight_;

    const std::string name_;
  };

 public:
  explicit BitmexRateTracker(const MarketExchangeApi& mea, const NativeClientConfig& config)
      : ord_rate_tracker_{nullptr}, all_rate_tracker_{nullptr}, rate_limit_report_writer_{nullptr} {
    using RateConfig = coin2::exchange::base::order::RateConfig;
    if (mea.market == coin::proto::MarketType::Futures) {
      coin2::exchange::order::RateLimitInfoFuturesBitmex rate_config;
      RateConfig::Get()->GetConfig(mea, config, &rate_config);
      InitRateTracker(&rate_config, mea.String(), EXAMPLE_FUTURES_BITMEX);
    } else {
      CHECK(false) << "unsupported mea: " << mea.String();
    }
  }

  template <class RateConfig>
  void
  InitRateTracker(RateConfig&& rate_config, std::string_view mea_str, std::string_view example) {
    if (!rate_config || !rate_config->has_req_all() || !rate_config->has_ord_sec()) {
      coin2::exchange::base::order::RateConfig::CheckFail(mea_str, example);
    }
    const auto& req_all = rate_config->req_all();
    const auto& ord_sec = rate_config->ord_sec();
    LOG(INFO) << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
              << "\nOG request rate was set as below:\n"
              << "\nreq_all: [" << req_all.reqs() << " (reqs) / " << req_all.secs() << " (seconds)]"
              << "\nord_sec: [" << ord_sec.reqs() << " (reqs) / " << ord_sec.secs() << " (seconds)]"
              << "\n\nOr you can configure customized rate in driver config. For example: "
              << example
              << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";

    auto ord_sec_ratio = ExtractRatioValveConfig(ord_sec);
    ord_rate_tracker_ = std::make_unique<BitmexRateTrackerImpl>(
        ord_sec.secs() * NS, /* window_size */
        ord_sec_ratio.ratio_valve_query_order,
        ord_sec_ratio.ratio_valve_maker_order,
        ord_sec_ratio.ratio_valve_taker_order,
        ord_sec_ratio.ratio_valve_amend_order,
        ord_sec_ratio.ratio_valve_cancel_order,
        "order_rate_tracker");
    ord_rate_tracker_->SetRateLimitInfo(ord_sec.reqs(), ord_sec.reqs());
    auto req_all_ratio = ExtractRatioValveConfig(req_all);
    all_rate_tracker_ = std::make_unique<BitmexRateTrackerImpl>(
        req_all.secs() * NS, /* window_size */
        req_all_ratio.ratio_valve_query_order,
        req_all_ratio.ratio_valve_maker_order,
        req_all_ratio.ratio_valve_taker_order,
        req_all_ratio.ratio_valve_amend_order,
        req_all_ratio.ratio_valve_cancel_order,
        "all_rate_tracker");
    all_rate_tracker_->SetRateLimitInfo(req_all.reqs(), req_all.reqs());

    // rate limit report
    rate_limit_report_writer_ = std::make_unique<RateLimitReportWriter>();
    rate_limit_report_writer_->SetRateLimitInfoString(rate_config);
  }

  auto ExtractRatioValveConfig(const coin2::exchange::order::RateLimitInfoItem& config)
      -> RatioValveConfig {
    RatioValveConfig ret;
    for (auto& [key, val] : config.ratio_valve_config()) {
      if (key == "query_order") {
        ret.ratio_valve_query_order = val;
      } else if (key == "maker_order") {
        ret.ratio_valve_maker_order = val;
      } else if (key == "taker_order") {
        ret.ratio_valve_taker_order = val;
      } else if (key == "amend_order") {
        ret.ratio_valve_amend_order = val;
      } else if (key == "cancel_order") {
        ret.ratio_valve_cancel_order = val;
      }
    }
    return ret;
  }

  void SetMakerOrder(bool is_maker_order) { is_maker_order_ = is_maker_order; }

  void SetRateLimitInfo(int32_t rate_limit, int32_t rate_limit_remaining) override { CHECK(false); }

  bool HasEnoughRateLimit(int64_t timestamp, ActionType action_type) override {
    bool ret = all_rate_tracker_->HasEnoughRateLimitByType(timestamp, action_type, is_maker_order_);
    if (ret && (action_type == ActionType::PLACE || action_type == ActionType::AMEND ||
                action_type == ActionType::CANCEL)) {
      ret = ord_rate_tracker_->HasEnoughRateLimitByType(timestamp, action_type, is_maker_order_);
    }
    if (!ret) {
      rate_limit_report_writer_->SetHitRateLimit(timestamp, action_type);
    }
    return ret;
  }

  void Push(int32_t num_reqs, int64_t timestamp, [[maybe_unused]] ActionType action_type) override {
    all_rate_tracker_->Push(num_reqs, timestamp);
    if (action_type == ActionType::PLACE || action_type == ActionType::AMEND ||
        action_type == ActionType::CANCEL) {
      ord_rate_tracker_->Push(num_reqs, timestamp);
    }
  }

  void ParseRateLimitInfo(
      [[maybe_unused]] const std::shared_ptr<AsioHttpContext>& http_context,
      [[maybe_unused]] ActionType action_type) override {}

  void SetRateLimitInfoString(const google::protobuf::Message* message) override {
    rate_limit_report_writer_->SetRateLimitInfoString(message);
  }

  void WriteRateLimitReportMessage(OrderLoggerCB callback) override {
    if (!callback) {
      return;
    }
    rate_limit_report_writer_->Write(callback);
  }

  double GetMaxRateUsedRatio(int64_t timestamp, ActionType action_type) override {
    switch (action_type) {
      case ActionType::PLACE:
      case ActionType::CANCEL:
      case ActionType::AMEND: {
        double all_ratio = 0.;
        if (all_rate_tracker_) {
          auto max_reqs = all_rate_tracker_->MaxReq();
          auto remaining = all_rate_tracker_->GetRemaining(timestamp);
          all_ratio = 1.0 * (max_reqs - remaining) / max_reqs;
        }
        double ord_ratio = 0.;
        if (ord_rate_tracker_) {
          auto max_reqs = ord_rate_tracker_->MaxReq();
          auto remaining = ord_rate_tracker_->GetRemaining(timestamp);
          ord_ratio = 1.0 * (max_reqs - remaining) / max_reqs;
        }
        return std::max(all_ratio, ord_ratio);
      }
      case ActionType::QUERY: {
        if (!all_rate_tracker_) {
          return 0.;
        }
        auto max_reqs = all_rate_tracker_->MaxReq();
        auto remaining = all_rate_tracker_->GetRemaining(timestamp);
        return 1.0 * (max_reqs - remaining) / max_reqs;
      }
      default: {
        // shouldn't reached here
        LOG(ERROR) << "ops~ something is wrong, action_type=" << action_type;
        return 0.;
      }
    }
  }

 private:
  std::unique_ptr<BitmexRateTrackerImpl> ord_rate_tracker_;
  std::unique_ptr<BitmexRateTrackerImpl> all_rate_tracker_;
  std::unique_ptr<RateLimitReportWriter> rate_limit_report_writer_;
  bool is_maker_order_ = false;
};
